Imports System.Runtime.InteropServices


Public Module Interpreter
	Public __builtins__ As PyModuleDict
	Public __main__ As PyObject
	Private __main__dict As PyModuleDict
	Private _format_exception As PyObject
	Private _str_join As PyObject

	'
	' initializes the interpreter. you must call this function prior to
	' doing anything with the python interpreter
	'
	Sub Init()
		Py_Initialize()
		PyEval_InitThreads()
		' create __builtins__
		__builtins__ = New PyModuleDict(PyEval_GetBuiltins())
		Builtins.Init()
		' create __main__
		Dim globals As New PyDict
		globals("__builtins__") = __builtins__
		__main__ = New PyObject(PyImport_ImportModuleEx("__main__", globals.p_self, globals.p_self, New PyTuple("*").p_self))
		__main__dict = New PyModuleDict(__main__.GetAttr("__dict__").p_self)
		' helper for exceptions
		_format_exception = Import("traceback").GetAttr("format_exception")
		_str_join = Builtins.str.GetAttr("join")
	End Sub

	'
	' finalizes the interpreter, destroys all living python objects 
	' (you'll want to call when your program exits, typically)
	'
	Sub Fini()
		Py_Finalize()
	End Sub

	'
	' locks the global interpreter lock ("GIL") from native threads.
	' your native thread must call this function before doing anything with
	' the python interpreter, and call ReleaseGIL when done. 
	' the return value is an opaque handle, which must be passed as is to
	' ReleaseGIL.
	' note: the main thread (the thread that called Py_Initialize) doesn't 
	' need to lock the gil.
	'
	Function LockGIL() As Integer
		Return PyGILState_Ensure()
	End Function

	'
	' releases the GIL from the calling thread. do not use any python APIs
	' after calling it. state is the opaque handle obtained from LockGil.
	' note: the main thread doesn't need to release the GIL
	'
	Sub ReleaseGIL(ByVal state As Integer)
		PyGILState_Release(state)
	End Sub

	'
	' imports the given module (returns a PyObject)
	'
	Function Import(ByVal modname As String) As PyObject
		Return New PyObject(PyImport_ImportModuleEx( _
		modname, __main__dict.p_self, __main__dict.p_self, New PyTuple("*").p_self), _
		False)
	End Function

	'
	' imports the given module (returns a PyObject), and adds it to __main__'s dict
	'
	Function ImportInto(ByVal modname As String) As PyObject
		Dim root As New PyObject(PyImport_ImportModuleEx( _
		modname, __main__dict.p_self, __main__dict.p_self, New PyTuple().p_self), _
		False)
		__main__.SetAttr(modname.Split("."c)(0), root)
		Return Import(modname)
	End Function

	'
	' evaluates the given python code (returns a native object or a PyObject)
	'
	Function Eval(ByVal code As String) As Object
		Return New PyObject(PyRun_String( _
		code, CAPI.CompilationMode.Eval, __main__dict.p_self, __main__dict.p_self), _
		False).Simplify()
	End Function

	'
	' executes the given python code
	'
	Sub Exec(ByVal code As String)
		PyRun_String(code, CAPI.CompilationMode.Exec, __main__dict.p_self, __main__dict.p_self)
		RaisePendingException()
	End Sub

	'
	' compiles the given python code, according to the mode
	'
	Function Compile(ByVal code As String, ByVal mode As CompilationMode, Optional ByVal filename As String = "?") As PyObject
		Return New PyObject(Py_CompileString(code, filename, mode), False)
	End Function

	'
	' indicates if an exception has occured
	'
	Function IsErr() As Boolean
		Return Not PyErr_Occurred().Equals(IntPtr.Zero)
	End Function

	'
	' class for all python exceptions
	' use `Matches(type)` to check if the python-exception's type matches some 
	' pythonic exception class
	'
	Class PythonException
		Inherits ApplicationException
		Public ReadOnly ExcType As PyObject
		Public ReadOnly ExcValue As PyObject

		Sub New(ByVal type As PyObject, ByVal value As PyObject, ByVal tbtext As String)
			MyBase.New(type.GetAttr("__name__").Str() & vbNewLine & tbtext)
			ExcType = type
			ExcValue = value
		End Sub

		Function Matches(ByVal type As PyObject) As Boolean
			Return CBool(PyErr_GivenExceptionMatches(ExcType.p_self, type.p_self))
		End Function

		Function Matches(ByVal ParamArray exc_tuple() As PyObject) As Boolean
			Dim tuple As PyTuple = New PyTuple(exc_tuple)
			Return CBool(PyErr_GivenExceptionMatches(ExcType.p_self, tuple.p_self))
		End Function
	End Class

	'
	' if an exception occured, clears it and throws a native (CLR) exception.
	' if no exception has occured, has no effect
	' note that the traceback is not always available (when there's no frame)
	'
	Sub RaisePendingException()
		Const sep As String = "  | "
		Dim ptype, pvalue, ptb As IntPtr
		Dim exc_type, exc_value, exc_tb, tblines As PyObject
		Dim tbtext As String = ""

		If Not IsErr() Then Exit Sub

		PyErr_Fetch(ptype, pvalue, ptb)
		PyErr_NormalizeException(ptype, pvalue, ptb)
		PyErr_Clear()

		' make objects out of the pointers
		exc_type = New PyObject(ptype)
		exc_value = New PyObject(pvalue)
		If ptb.Equals(IntPtr.Zero) Then
			tbtext = sep + exc_value.Str() + vbNewLine
		Else
			exc_tb = New PyObject(ptb)
			tblines = _format_exception.Call(exc_type, exc_value, exc_tb)
			tbtext = sep + _str_join.Call(sep, tblines).Str()
		End If
		tbtext += "  +----------------------------------------------------------------------------"

		' throw a native exception
		Throw New PythonException(exc_type, exc_value, tbtext)
	End Sub
End Module
